from data_type import *


class memory:
    def __init__(self, size):
        self.files = [byte() for _ in range(size)]
        self.size = size

    def data_64_port_out(self, addr):
        byte0, byte1, byte2, byte3, \
        byte4, byte5, byte6, byte7 = self.files[addr].val, self.files[addr + 1].val, \
                                     self.files[addr + 2].val, self.files[addr + 3].val, \
                                     self.files[addr + 4].val, self.files[addr + 5].val, \
                                     self.files[addr + 6].val, self.files[addr + 7].val
        return byte_to_bit64(byte7, byte6, byte5, byte4, byte3, byte2, byte1, byte0)

    def data_64_port_in(self, bits, addr):
        byte7, byte6, byte5, byte4, byte3, byte2, byte1, byte0 = bit64_to_byte(bits)
        self.files[addr] = byte(byte0)
        self.files[addr + 1] = byte(byte1)
        self.files[addr + 2] = byte(byte2)
        self.files[addr + 3] = byte(byte3)
        self.files[addr + 4] = byte(byte4)
        self.files[addr + 5] = byte(byte5)
        self.files[addr + 6] = byte(byte6)
        self.files[addr + 7] = byte(byte7)

    def data_32_port_out(self, addr):
        byte0, byte1, byte2, byte3 = self.files[addr].val, self.files[addr + 1].val, \
                                     self.files[addr + 2].val, self.files[addr + 3].val
        return byte_to_bit32(byte3, byte2, byte1, byte0)

    def data_32_port_in(self, bits, addr):
        byte3, byte2, byte1, byte0 = bit32_to_byte(bits)
        self.files[addr] = byte(byte0)
        self.files[addr + 1] = byte(byte1)
        self.files[addr + 2] = byte(byte2)
        self.files[addr + 3] = byte(byte3)

    def data_16_port_in(self, bits, addr):
        byte1, byte0 = bit16_to_byte(bits)
        self.files[addr] = byte(byte0)
        self.files[addr + 1] = byte(byte1)

    def data_16_port_out(self, addr):
        byte0, byte1 = self.files[addr].val, self.files[addr + 1].val
        return byte_to_bit16(byte1, byte0)

    def data_8_port_in(self, bits, addr):
        self.files[addr] = byte(bits)

    def data_8_port_out(self, addr):
        return self.files[addr].val

    def output(self):
        for i in range(0x1000, 0x1000 + 64, 8):
            print("0x%08x:" % i, end=" ")
            for j in range(8):
                print("%02x" % self.files[i + j].val, end="\t")
            print("")


class page:
    def __init__(self, able=False, frame=-1, protect=False):
        self.able = able
        self.frame = frame
        self.protect = protect


def to_lin_addr(vir_addr, segment, base):
    top = 0x4000800000 - 0x80000
    if segment == 1:
        return vir_addr - base  # data
    elif segment == 2:
        return vir_addr - top  # stack
    else:
        return vir_addr - base  # text


class queue:
    def __init__(self, size):
        self.max_size = size
        self.data = []
        self.size = 0

    def in_que(self, num, frame):
        if self.size + 1 <= self.max_size:
            self.data.append((num, frame))
            self.size += 1

    def out_que(self):
        if self.size >= 0:
            self.size -= 1
            ans = self.data.pop(0)
            return ans

    def __contains__(self, x):
        for i in range(self.size):
            if self.data[i][1] == x:
                return True
        return False


class MMU:
    def __init__(self, lin_size=128, phy_size=16, base=0x10000):
        self.base = base
        self.size = phy_size
        self.que = queue(phy_size)
        self.table = [page() for _ in range(lin_size)]

    def to_phy_addr(self, vir_addr, segment, mem):
        lin_addr = to_lin_addr(vir_addr, segment, self.base)
        page_num = lin_addr >> 12
        if self.table[page_num].able:
            frame = self.table[page_num].frame
            return (frame << 12) | (lin_addr & 0xfff)
        else:
            do_no_page(self.que, page_num, self.table, mem)
            if segment:
                self.table[page_num].protect = True
            frame = self.table[page_num].frame
            return (frame << 12) | (lin_addr & 0xfff)


def do_no_page(que, page_num, page_table, mem: memory):
    if que.size >= que.max_size:
        out = que.out_que()
        while page_table[out[0]].protect:
            que.in_que(out)
            out = que.out_que()
        frame = page_table[out[0]].frame
        phy_addr = frame << 12
        filename = "mem_file/disk/memory" + str(out[0])
        file = open(filename, "wb")
        for off_s in range(0x1000):
            num = mem.files[phy_addr + off_s].val.to_bytes(1, "little")
            file.write(num)

        file.close()

        page_table[out[0]].frame = -1
        page_table[out[0]].able = False

    frame = -1
    for i in range(que.max_size):
        if i not in que:
            frame = i
            break
    phy_addr = frame << 12
    filename = "mem_file/disk/memory" + str(page_num)
    file = open(filename, "rb")
    for off_s in range(0x1000):
        byte_t = file.read(1)
        mem.files[off_s + phy_addr] = byte(int.from_bytes(byte_t, "little"))
    file.close()

    page_table[page_num].frame = frame
    page_table[page_num].able = True
    que.in_que(page_num, frame)


# table : name, addr, offset, size, filename
def load_instant(mem: memory, mmu: MMU, table):
    for name, addr, offset, size, filename in table:
        infile = open(filename, "rb")
        if name == ".text":
            segment = 0
        else:
            segment = 1
        vir_addr = addr
        last = size
        while last > 0:
            phy_addr = mmu.to_phy_addr(vir_addr, segment, mem)
            save_size = min(0x1000 - (phy_addr & 0xfff), last)
            last -= save_size
            vir_addr += save_size
            for off_s in range(save_size):
                byte_t = infile.read(1)
                mem.files[off_s + phy_addr] = byte(int.from_bytes(byte_t, "little"))
        infile.close()
